import datetime
import time
import calendar as _calendar

from tags.templatetags.strip_html import strip_html
from django.utils.datastructures import SortedDict
from django import template

register = template.Library()

#Modified from gencal by Justin Lilly <https://github.com/justinlilly/django-gencal>

fixed_holidays = {(1,1):("New Years Day"),(1,7):("Eastern Orthodox Christmas"),
              (1,24):("Global Bellylaugh Day"),
              (2,2):("Groundhog Day"), (2,14):("Valentines Day"),
              (2,15):("Canadian Flag Day"),
              (3,14):("PI Day"),(3,17):("St. Patrick's Day"),(3,25):("Tolkein Day"),
              (4,1):("April Fool's Day"),(4,5):("Fun at Work Day"),(4,22):("Earth Day"),
              (5,1):("May Day"),(5,25):("Towel Day"),
              (6,22):("Stupid Guy Thing Day"),
              (7,1):("Canada Day"),(7,4):("Independence Day"),
              (7,13):("Embrace Your Geekness Day"),(7,28):("Cowboy Day"),
              (8,7):("Particularly Preposterous Pkg Day"),
              (8,18):("Bad Poetry Day"),(8,25):("Kiss and Make Up Day"),
              (9,19):("Talk Like a Pirate Day"),(9,22):("World Carfree Day"),
              (9,25):("One Hit Wonder Day"),
              (10,6):("Mad Hatter Day"),(10,12):("Moment of Frustration Scream Day"),
              (10,31):("Hallowe'en"),
              (11,3):("Cliche Day"),(11,8):("Abet and Aid Punsters Day"),
              (11,11):("Remembrance / Veterans Day"),
              (12,5):("Bathtub Party Day"),
              (12,23):("Festivus"),(12,24):("Christmas Eve"),
              (12,25):("Christmas Day"),(12,26):("Boxing Day / Kwanzaa"),
              (12,31):("New Years Eve")}
# indexed by month and day of week, follow with occurrence in month
floating_holidays = {(1,1):(3,"Martin Luther King Day"),
              (2,1):(3,"President's Day / Family Day"),
              (4,4):(3,"High Five Day"),
              (5,0):(2,"Mother's Day"),
              (6,0):(3,"Father's Day"),
              (8,1):(1,"Canadian Heritage Day"),
              (9,1):(1,"Labour Day"),(9,6):(3,"Software Freedom Day"),
              (10,1):(2,"Cdn Thanksgiving / Columbus Day"),
              (11,4):(4,"USA Thanksgiving")}
def get_nth_day_of_month(n, weekday, month, year):
    HAVE_30_DAYS = (4,6,9,11)
    HAVE_31_DAYS = (1,3,5,7,8,10,12)

    firstday, daysinmonth = _calendar.monthrange(year, month)
    firstday = (firstday+1)%7 #calendar returns weekdays as Mon-Sun, we want Sun-Sat
    
    # firstday is MON, weekday is WED -- start with 3rd day of month
    # firstday is WED, weekday is MON --
    # firstday = weekday
    if firstday < weekday:
        date = weekday - firstday + 1 # 2 - 0 + 1
    elif firstday > weekday:
        date = 7 - (firstday - weekday) + 1
    else:
        date = 1

    if n == 1:
        return date

    for i in range(1, n):
        date += 7
        if month in HAVE_30_DAYS and date > 30:
            raise IndexError
        if month in HAVE_31_DAYS and date > 31:
            raise IndexError
        if month == 2 and date > 28:
            ignore, daysinfeb = _calendar.monthrange(year, 2)
            if date > daysinfeb:
                raise IndexError

    return date
    
def get_easter(year):
        y = year
        
        # formula taken from http://aa.usno.navy.mil/faq/docs/easter.html
        c = (y / 100)
        n = y - 19 * ( y / 19 )
        k = ( c - 17 ) / 25
        i = c - c / 4 - ( c - k ) / 3 + 19 * n + 15
        i = i - 30 * ( i / 30 )
        i = i - ( i / 28 ) * ( 1 - ( i / 28 ) * ( 29 / ( i + 1 ) )
                               * ( ( 21 - n ) / 11 ) )
        j = y + y / 4 + i + 2 - c + c / 4
        j = j - 7 * ( j / 7 )
        l = i - j
        m = 3 + ( l + 40 ) / 44
        d = l + 28 - 31 * ( m / 4 )

        return datetime.date(y, m, d)
    
class CalendarNode(template.Node):
    def __init__(self, objects=None, year=None, month=None):
        today = datetime.date.today()
        self.year = year or today.year
        self.month = month or today.month
        self.objects = objects or []
        
    def render(self, context):
        try: self.year = int(template.Variable(self.year).resolve(context))
        except: self.year = int(self.year)
        try: self.month = int(template.Variable(self.month).resolve(context))
        except: self.month = int(self.month)
        try: self.objects = template.Variable(self.objects).resolve(context) if self.objects else []
        except: self.objects = []
        return ''.join(ListCalendar(self.objects,self.year,self.month,_calendar.SUNDAY).formatmonth(self.year, self.month))

def calendar(parser, token):
    #obj_list=[], year=None, month=None, calendar_class=None):
    year = None
    month = None
    objects = None
    tokens = token.contents.split()
    for token in tokens:
        if token.startswith('year='):
            year = token[5:]
        elif token.startswith('month='):
            month = token[6:]
        elif token.startswith('objects='):
            objects = token[8:]
        else:
            objects = token

    #return ''.join(cls(obj_list).formatmonth(year, month))
    return CalendarNode(objects, year, month)
register.tag(calendar)

class ListCalendar(_calendar.HTMLCalendar):
    """
    This is a calendar object which accepts a ``list`` argument and a
    ``date_field`` keyword argument.. This class will return an HTML
    calendar with links on the days that are present in the list,
    using ``date_field`` as the lookup.

    :param cal_items: A list of items to put in the calendar.
    :type cal_items: list.
    :keyword year: Year to render.
    :type year: int.
    :keyword month: Month to render.
    :type month: int.
    """

    def __init__(self, cal_items=[], year=None, month=None, *args, **kwargs):
        today = datetime.date.today()
        if year == None:
            year = today.year
        self.year = year

        if not month:
            month = today.month
        self.month = month

        self.date_field = kwargs.pop('date_field', 'date')

        super(ListCalendar, self).__init__(*args, **kwargs)

        cal_arr = self.monthdatescalendar(year, month)
        month_dict = SortedDict()
        holiday_dict = SortedDict()
        for week in cal_arr:
            for date in week:
                month_dict[date] = []
                holiday_dict[date] = []

        for item in cal_items:
            if isinstance(item, dict):
                possible_date = item.get(self.date_field)
            else:
                possible_date = getattr(item, self.date_field)
            if type(possible_date) == datetime.datetime:
                # transform possible_date to a date, not a datetime
                possible_date = possible_date.date()
            if possible_date and possible_date.month == self.month:
                month_dict[possible_date].append(item)
        for date_tuple,name in fixed_holidays.items():
            if date_tuple[0] == self.month:
                holiday = datetime.date(self.year,self.month,date_tuple[1])
                holiday_dict[holiday].append({'date':'','title':name,'description':name,'link':'','css_class':'holiday'})
        for date_tuple,name_tuple in floating_holidays.items():
            if date_tuple[0] == self.month:
                holiday = datetime.date(self.year,self.month,get_nth_day_of_month(name_tuple[0], date_tuple[1], date_tuple[0], self.year))
                holiday_dict[holiday].append({'date':'','title':name_tuple[1],'description':name_tuple[1],'link':'','css_class':'holiday'})
        easter = get_easter(self.year)
        if easter.month == self.month:
            holiday_dict[easter].append({'date':'','title':'Easter Sunday','description':'Easter Sunday','link':'','css_class':'holiday'})
        good_friday = easter - datetime.timedelta(days=2)
        if good_friday.month == self.month:
            holiday_dict[good_friday].append({'date':'','title':'Good Friday','description':'Good Friday','link':'','css_class':'holiday'})
        mardi_gras = easter - datetime.timedelta(days=47)
        if mardi_gras.month == self.month:
            holiday_dict[mardi_gras].append({'date':'','title':'Mardi Gras','description':'Mardi Gras','link':'','css_class':'holiday'})
        self.month_dict = month_dict
        self.holiday_dict = holiday_dict

    def formatday(self, day, weekday):
        """
        Return a day as a table cell.

        :arg day: A day to be formatted.
        :type day: date object.
        :arg weekday: Weekday of given day.
        :type weekday: int.
        """
        items = self.month_dict[day] if day in self.month_dict else []
        holidays = self.holiday_dict[day] if day in self.holiday_dict else []
        holiday_text = ''
        for holiday in holidays:
            holiday_text += "%s\n" % holiday['title']
        holiday_text = holiday_text[:-1]
        Return = '<td class="%s"><div class="date"><span class="%s %s" %s>%d</span></div>' % ('shaded' if day.month != self.month else '','holiday' if holidays else '', 'extra_padding' if day.day < 10 else '', 'title="%s"' % holiday_text if holidays else '', day.day)
        for item in items:   
            link = '<a href="%s" title="%s">%s</a>' % (item['link'], strip_html(item['description']), strip_html(item['title'])) if item['link'] else strip_html(item['title'])
            Return += '<div class="news_calendar_item %s" title="%s">%s</div>' % (item['css_class'] if 'css_class' in item else '', strip_html(item['description']), link)
        return Return + '</td>'              


    def get_link(self, dt):
        """
        Should return a url to a given date, as represented by ``dt``
        
        :arg dt: date to turn into a url
        :type dt: date/datetime
        """
        return None

    def monthdates2calendar(self, year, month):
        """
        Function returns a list containing a list of tuples
        (representing a week), the tuples represent a
        ``datetime.date`` for the given day and the weekday associated
        with it.

        This is something that ought to be implemented in core. We
        have monthdatescalendar, monthdayscalendar,
        monthdays2calendar, but no monthdates2calendar.

        :keyword year: Year to render.
        :type year: int.
        :keyword month: Month to render.
        :type month: int.
        :returns: Tuple of (datetime, weekday).
        :rtype: tuple(datetime, int)
        """
        return [[(dt, dt.weekday()) for dt in week] for week in self.monthdatescalendar(year, month)]

    def formatmonth(self, theyear, themonth, withyear=True):
        """
        Return a formatted month as a table.

        Overridden so weeks will use monthdates2calendar, so we have
        access to full date objects, rather than numbers.

        :arg theyear: Year of calendar to render.
        :type theyear: int.
        :arg themonth: Month of calendar to render
        :type themonth: int.
        :keyword withyear: If true, it will show the year in the header.
        :type withyear: bool.
        """
        v = []
        a = v.append
 
        a('<table border="1" cellpadding="0" cellspacing="0" class="news_calendar">')
        a('\n')
        a(self.formatmonthname(theyear, themonth, withyear=withyear))
        a('\n')
        a(self.formatweekheader())
        a('\n')
        for week in self.monthdates2calendar(theyear, themonth):
            a(self.formatweek(week))
            a('\n')
        a('</table>')
        a('\n')
        return v